package mop.security.acl;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

/**
 * Basic Access control lists implementation
 * 
 * Access control lists consist of roles, assets, privileges & assertions.
 * 
 * Role = owner to give access / deny access to a asset. Null = all owners
 * Asset = asset to give give/deny access to. Null = all assets
 * Privileges = certain privileges, e.g. create, modify, ... Null = all privileges
 * Assertion = certain assertions before allowing / denying access, brings more flexibility.
 * 
 * There is no hierarchy of specificness, newer rules always overwrite older rules.
 * When no matching rule is found, access is always denied
 * 
 * @author MOP Team 7
 */
public class Acl {
	
	/**
	 * List of acl rules
	 */
	List<Rule> rules = new LinkedList<Rule>();
	
	/**
	 * Registry of ACL role types
	 */
	HashMap<String,Role> roles = new HashMap<String, Role>();
	
	/**
	 * Registry of ACL asset types
	 */
	HashMap<String,Asset> assets = new HashMap<String, Asset>();
	
	
	/**
	 * Adds an access rule to the ACL
	 * 
	 * @param rolestr		ID of the role		| null = all owners
	 * @param assetstr	ID of the asset 	| null = all assets
	 * @param privileges	Privileges			| null = all privileges
	 * @param assertion		Assertion			| null = no assertion
	 * @param type			Type 				| allow / deny
	 * 
	 * @effect Rule is added
	 * 	|rules.add(new Rule(role, asset, privileges, assertion, type))
	 */
	protected void addRule(String rolestr, String assetstr, String[] privileges, Assertion assertion, Type type){
		if(assertion == null)
			assertion = new NoAssertion();
		
		//Make sure we keep 1 and only 1 of each role in registry
		Role role = null;
		if(rolestr != null){
			if(!roles.containsKey(rolestr)){
				roles.put(rolestr, new Role(rolestr));
			}
			role = roles.get(rolestr);
		}
		
		//Make sure we keep 1 and only 1 of each asset in registry
		Asset asset = null;
		if(assetstr != null){
			if(!assets.containsKey(assetstr)){
				assets.put(assetstr, new Asset(assetstr));
			}
			asset = assets.get(assetstr);
		}
		
		//Add the rule to the acl
		rules.add(new Rule(role, asset, privileges, assertion, type));
	}
	
	
	/**
	 * Allow specified access
	 * @effect
	 * 	this#allow(role.getRoleId(),asset.getAssetId(),privileges,assertion)
	 */
	public void allow(RoleInterface role, AssetInterface asset, String[] privileges, Assertion assertion){
		addRule(role.getRoleId(),asset.getAssetId(),privileges,assertion,Type.ALLOW);
	}
	
	
	/**
	 * Allow specified access
	 * @effect
	 * 	this#addRule(role,asset,privileges,assertion,Type.ALLOW)
	 */
	public void allow(String role, String asset, String[] privileges, Assertion assertion){
		addRule(role,asset,privileges,assertion,Type.ALLOW);
	}
	
	/**
	 * Deny specified access
	 * @effect
	 * 	this#deny(role.getRoleId(),asset.getAssetId(),privileges,assertion)
	 */
	public void deny(RoleInterface role, AssetInterface asset, String[] privileges, Assertion assertion){
		deny(role.getRoleId(),asset.getAssetId(),privileges,assertion);
	}

	
	/**
	 * Deny specified access
	 * @effect
	 * 	this#addRule(role,asset,privileges,assertion,Type.DENY)
	 */
	public void deny(String role, String asset, String[] privileges, Assertion assertion){
		addRule(role,asset,privileges,assertion,Type.DENY);
	}
	
	/**
	 * Check if role has access to asset with all privileges
	 * @effect
	 * 	this#isAllowed(role,asset,null);
	 */
	public boolean isAllowed(Role role, Asset asset){
		return isAllowed(role,asset,null);
	}
	
	/**
	 * Check if role has access to asset with certain privileges
	 * @param role		Role		|	null = all roles
	 * @param asset	Asset	|	null = all assets
	 * @param privilege	Privileges	|	null = all privileges
	 * @return	True if access is granted, false in any other case
	 */
	public boolean isAllowed(RoleInterface role, AssetInterface asset, String privilege){
		for(int i = rules.size() - 1; i >=0; i--){	//Iterate in reverse way, so newer rules overwrite older rules
			Rule r = rules.get(i);
			if(r.definesRole(role) && r.definesAsset(asset)){
				//Match role & asset
				if(r.definesPrivilege(privilege) && r.getAssertion().assertTrue(this, role, asset, privilege)){
					// Match privileges & assertions
					return r.getType().isAllowed();
				}
			}
		}
		return false;
	}
	
	/**
	 * Check if role has access to asset with all privileges
	 * @effect
	 *  isAllowed(role, asset, null)
	 */
	public boolean isAllowed(String role, String asset){
		return isAllowed(new Role(role), new Asset(asset), null);
	}
	
	/**
	 * Check if role has access to asset with certain privileges
	 * @effect
	 *  isAllowed(new Role(role), new Asset(asset), privilege)
	 */
	public boolean isAllowed(String role, String asset, String privilege){
		return isAllowed(new Role(role), new Asset(asset), privilege);
	}
}
